package com.jar.exercise.多线程.延时队列;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        DelayQueue<ItemVo<Order>> queue = new DelayQueue<>();
        // 插入订单
        new Thread(new PutOrder(queue)).start();
        // 取出过期订单的线程
        new Thread(new FetchOrder(queue)).start();

        // 为了看到效果，这里没个一秒打印一次时间，一共15秒，打印15次。
        for (int i = 1; i <= 15; i++) {
            Thread.sleep(1000);
            System.out.println("=========================" + i);
        }

    }
}

@Getter
class ItemVo<T> implements Delayed {
    // 到期时间 单位：ms
    private long activeTime;
    // 订单实体（使用泛型是因为后续扩展其他业务共用此业务类）
    private T data;

    public ItemVo(long activeTime, T data) {
        // 将传入的时间转换为超时的时刻
        this.activeTime = TimeUnit
                .NANOSECONDS
                .convert(
                        activeTime,
                        TimeUnit.MILLISECONDS
                )
            + System.nanoTime();
        this.data = data;
    }


    // 按照剩余时间进行排序
    @Override
    public int compareTo(Delayed o) {
        // 订单剩余时间-当前传入的时间= 实际剩余时间（单位纳秒）
        long d = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
        // 根据剩余时间判断等于0 返回1 不等于0
        // 有可能大于0 有可能小于0  大于0返回1  小于返回-1
        return (d == 0) ? 0 : ((d > 0) ? 1 : -1);
    }

    // 获取剩余时间
    @Override
    public long getDelay(TimeUnit unit) {
        // 剩余时间= 到期时间-当前系统时间，系统一般是纳秒级的，所以这里做一次转换
        return unit.convert(activeTime - System.nanoTime(), TimeUnit.NANOSECONDS);
    }

}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Order {
    // 订单编号
    private String orderId;
    // 订单金额
    private Double orderMoney;

}

class PutOrder implements Runnable {

    // 使用DelayQueue：一个使用优先级队列实现的无界阻塞队列。
    private final DelayQueue<ItemVo<Order>> queue;

    public PutOrder(DelayQueue<ItemVo<Order>> queue) {
        this.queue = queue;
    }

    /**
     * 这里模拟淘宝、京东、苏宁的订单，淘宝是5秒到期，京东是10秒到期，苏宁是15秒到期
     */
    @Override
    public void run() {
        // 京东订单插入
        Order jdOrder = new Order("jd002", 19.9);
        ItemVo<Order> itemVoJd = new ItemVo<>(10000, jdOrder);
        queue.offer(itemVoJd);
        System.out.println("京东订单10秒后过期：" + jdOrder.getOrderId());

        // 苏宁订单插入
        Order snOrder = new Order("sn003", 29.9);
        ItemVo<Order> itemVoSn = new ItemVo<>(15000, snOrder);
        queue.offer(itemVoSn);
        System.out.println("苏宁订单15秒后过期：" + snOrder.getOrderId());
        // 淘宝订单插入
        Order tbOrder = new Order("tb001", 9.9);
        ItemVo<Order> itemVoTb = new ItemVo<>(5000, tbOrder);
        queue.offer(itemVoTb);
        System.out.println("淘宝订单5秒后过期：" + tbOrder.getOrderId());

    }
}

class FetchOrder implements Runnable {

    // 使用DelayQueue：一个使用优先级队列实现的无界阻塞队列。
    private DelayQueue<ItemVo<Order>> queue;

    public FetchOrder(DelayQueue<ItemVo<Order>> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                // 使用DelayQueue的take方法获取当前队列里的元素(take方法是阻塞方法，如果队列里有值则取出，否则一直阻塞)
                ItemVo<Order> itemVo = queue.take();
                // 获取元素的实体对象，保险起见做一次强制转型
                Order order = itemVo.getData();
                System.out.println("订单：" + order.getOrderId() + " 已过期！已从订单队列里剔除！");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

}